package simpledb.index;

import simpledb.common.DbException;
import simpledb.common.Debug;
import simpledb.common.Type;
import simpledb.storage.BufferPool;
import simpledb.storage.Field;
import simpledb.storage.IntField;
import simpledb.storage.Page;
import simpledb.transaction.TransactionId;

import java.io.*;
import java.util.Arrays;

/**
 * BTreeHeaderPage 表示 B 树索引文件中一个页面的头部信息。
 * 每个实例对应一个磁盘页（header page），用于记录该页中哪些 slot 是空闲的或已被占用。
 *
 * 实现了 Page 接口，因此可以被 BufferPool 管理。
 *
 * @see BTreeFile
 * @see BufferPool
 */
public class BTreeHeaderPage implements Page {

    // 页面是否被修改过
    private volatile boolean       dirty       = false;
    // 修改此页面的事务 ID
    private volatile TransactionId dirtier     = null;

    // 每个 int 字段占 4 字节
    final static int               INDEX_SIZE  = Type.INT_TYPE.getLen();

    // 当前页面的 PageId
    final BTreePageId              pid;
    // 页面头部数据字节数组
    final byte[]                   header;
    // 头部中可用 slot 的数量（每个 byte 可表示 8 个 slot）
    final int                      numSlots;

    // 下一个和上一个 header 页面的页号
    private int                    nextPage;
    private int                    prevPage;

    // 旧的数据副本，用于恢复
    byte[]                         oldData;
    private final Byte             oldDataLock = (byte) 0;

    /**
     * 从磁盘读取的字节数据中创建一个 BTreeHeaderPage。
     * 页面格式包含两个指针（next 和 prev）和一系列表示页槽使用情况的字节。
     *
     * @param id   当前页面的 PageId
     * @param data 页面的原始字节数据
     * @throws IOException 如果解析失败
     */
    public BTreeHeaderPage(BTreePageId id, byte[] data) throws IOException {
        this.pid = id;
        this.numSlots = getNumSlots();
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));

        // 读取 next 和 prev 指针
        try {
            Field f = Type.INT_TYPE.parse(dis);
            this.nextPage = ((IntField) f).getValue();
        } catch (java.text.ParseException e) {
            e.printStackTrace();
        }

        try {
            Field f = Type.INT_TYPE.parse(dis);
            this.prevPage = ((IntField) f).getValue();
        } catch (java.text.ParseException e) {
            e.printStackTrace();
        }

        // 分配并读取 header 数据
        header = new byte[getHeaderSize()];
        for (int i = 0; i < header.length; i++)
            header[i] = dis.readByte();

        dis.close();

        setBeforeImage(); // 设置恢复用的旧数据
    }

    /**
     * 初始时将所有 slot 标记为已使用（全 1）
     */
    public void init() {
        Arrays.fill(header, (byte) 0xFF);
    }

    /**
     * 计算 header 区域所占字节数（除去两个指针的空间）
     */
    private static int getHeaderSize() {
        int pointerBytes = 2 * INDEX_SIZE; // nextPage + prevPage
        return BufferPool.getPageSize() - pointerBytes;
    }

    /**
     * 计算 header 中的 slot 数量（每个 byte 表示 8 个 slot）
     */
    public static int getNumSlots() {
        return getHeaderSize() * 8;
    }

    /**
     * 返回此页面未被修改前的状态（用于恢复）
     */
    public BTreeHeaderPage getBeforeImage() {
        try {
            byte[] oldDataRef = null;
            synchronized (oldDataLock) {
                oldDataRef = oldData;
            }
            return new BTreeHeaderPage(pid, oldDataRef);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1); // 不应发生
        }
        return null;
    }

    /**
     * 设置页面修改前的数据副本
     */
    public void setBeforeImage() {
        synchronized (oldDataLock) {
            oldData = getPageData().clone();
        }
    }

    /**
     * 返回与此页面关联的 PageId
     */
    public BTreePageId getId() {
        return pid;
    }

    /**
     * 将当前页面内容转换为字节数组，用于持久化存储。
     * 保证可以通过构造函数重建相同的页面对象。
     */
    public byte[] getPageData() {
        int len = BufferPool.getPageSize();
        ByteArrayOutputStream baos = new ByteArrayOutputStream(len);
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            dos.writeInt(nextPage);
            dos.writeInt(prevPage);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 写入 header 数据
        for (byte b : header) {
            try {
                dos.writeByte(b);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return baos.toByteArray();
    }

    /**
     * 静态方法，生成一个代表空 BTreeHeaderPage 的字节数组。
     * 所有字节初始值为 0。
     */
    public static byte[] createEmptyPageData() {
        int len = BufferPool.getPageSize();
        return new byte[len]; // 全 0
    }

    /**
     * 获取前一个 header 页面的 PageId
     */
    public BTreePageId getPrevPageId() {
        if (prevPage == 0) {
            return null;
        }
        return new BTreePageId(pid.getTableId(), prevPage, BTreePageId.HEADER);
    }

    /**
     * 获取下一个 header 页面的 PageId
     */
    public BTreePageId getNextPageId() {
        if (nextPage == 0) {
            return null;
        }
        return new BTreePageId(pid.getTableId(), nextPage, BTreePageId.HEADER);
    }

    /**
     * 设置前一个 header 页面的 PageId
     */
    public void setPrevPageId(BTreePageId id) throws DbException {
        if (id == null) {
            prevPage = 0;
        } else {
            if (id.getTableId() != pid.getTableId()) {
                throw new DbException("表ID不匹配");
            }
            if (id.pgcateg() != BTreePageId.HEADER) {
                throw new DbException("必须是 header 类型页面");
            }
            prevPage = id.getPageNumber();
        }
    }

    /**
     * 设置下一个 header 页面的 PageId
     */
    public void setNextPageId(BTreePageId id) throws DbException {
        if (id == null) {
            nextPage = 0;
        } else {
            if (id.getTableId() != pid.getTableId()) {
                throw new DbException("表ID不匹配");
            }
            if (id.pgcateg() != BTreePageId.HEADER) {
                throw new DbException("必须是 header 类型页面");
            }
            nextPage = id.getPageNumber();
        }
    }

    /**
     * 标记此页面是否被修改，并记录修改的事务
     */
    public void markDirty(boolean dirty, TransactionId tid) {
        this.dirty = dirty;
        if (dirty)
            this.dirtier = tid;
    }

    /**
     * 返回最后一次修改此页面的事务ID（若页面干净则返回 null）
     */
    public TransactionId isDirty() {
        return this.dirty ? this.dirtier : null;
    }

    /**
     * 检查指定索引的 slot 是否被使用
     */
    public boolean isSlotUsed(int i) {
        int headerbit = i % 8;
        int headerbyte = (i - headerbit) / 8;
        return (header[headerbyte] & (1 << headerbit)) != 0;
    }

    /**
     * 标记某个 slot 为使用或未使用状态
     */
    public void markSlotUsed(int i, boolean value) {
        int headerbit = i % 8;
        int headerbyte = (i - headerbit) / 8;

        Debug.log(1, "BTreeHeaderPage.setSlot: 设置 slot %d 为 %b", i, value);
        if (value)
            header[headerbyte] |= 1 << headerbit;
        else
            header[headerbyte] &= (0xFF ^ (1 << headerbit));
    }

    /**
     * 获取第一个空闲的 slot 索引
     * @return 第一个空闲 slot 的索引，若无空闲则返回 -1
     */
    public int getEmptySlot() {
        for (int i = 0; i < header.length; i++) {
            if ((int) header[i] != 0xFF) { // 存在空位
                for (int j = 0; j < 8; j++) {
                    if (!isSlotUsed(i * 8 + j)) {
                        return i * 8 + j;
                    }
                }
            }
        }
        return -1;
    }
}
